<?php
/**
 * Глобальный класс управления пользователями
 * 
 * @category Phorm
 * @package User
 * @name /Phorm/User.php
 * @copyright phorm.ru
 * @since 30.11.2010
 * @author vladimir@kachalov.net
 */

class Phorm_User extends Phorm_Model {

	protected $_userinfo;
	
	/**
	 * Возвращает массив опций для построения таблицы Phorm_JQuery_DataGrid
	 *
	 * @param string $section Секция xml-файла
	 * @return array
	 */
	
	public function getGridOptions($section = 'index') {
		
		$options = Phorm_Config::xml('grids/control/users.xml',$section);
		
		// Добавляем список ролей на поисковую форму
		if(isset($options['search']['roleid'])) {
			$options['search']['roleid']['optiondata'] = $this->getRolesListAsPairs();
		}
		
		return $options;
		
	}
	
	/**
	 * Возвращает список пользователей
	 *
	 * @param array $options Массив условий для выборки
	 * @return array
	 */
	
	public function getUsersList($options = array()) {
		
		$select = $this->_db->select()
				-> from(array('AA'=>'pw_users'), new Zend_Db_Expr("SQL_CALC_FOUND_ROWS AA.*, 'actions' actions"))
				-> joinLeft(array('BB'=>'pw_roles'),"AA.roleid=BB.roleid AND BB.isexist='yes'",array('rolename','rolecode'))
				-> where('AA.isexist=?','yes');
				
		if($this->getModule()!="control") $select -> where('visible=?','yes');
				
		if(isset($options['roleid']) && trim($options['roleid'])!='') 
				$select->where('AA.roleid=?',$options['roleid']);
				
		if(isset($options['username']) && trim($options['username'])!='')
				$select->where('AA.username=?',$options['username']);
				
		if(isset($options['signed_from']) && trim($options['signed_from'])!='')
				$select->where('AA.signed>=?',strtotime($options['signed_from']));
		
		if(isset($options['signed_to']) && trim($options['signed_to'])!='')
				$select->where('AA.signed<=?',strtotime($options['signed_to']));
				
		if(isset($options['sortField']) && in_array($options['sortField'],array('userid','signed','username','lastvisit','rolename','lastip','visible'))) {
			$sortOrder = isset($options['sortOrder']) && $options['sortOrder']=='desc' ? ' DESC' : ' ASC';
			$select->order($options['sortField'].$sortOrder);
		} else {
			$select->order('username');
		}
				
		return $this->_db->fetchAll($select);
		
	}
	
	/**
	 * Возвращает информацию о пользователе
	 * 
	 * @param array $options Массив опций
	 *
	 * @return array Массив данных о пользователе
	 */
		
	public function getUserInfo($options) {
		
		$options = $this->getFilteredValuesByTableCols('pw_users',$options);
		
		$select = $this->_db->select()
				-> from('pw_users')
				-> where('isexist=?','yes');
					  			  
		foreach ($options as $k=>$v) $select->where($k.'=?',$v);
			
		if($row = $this->_db->fetchRow($select)) {

			// Получаем информацию об уровне доступа к MVC-ресурсам
			$row['acl']['mvc'] = $this->getRoleMvcAcl($row['roleid']);
				
			// Получаем информацию об уровне доступа к разделам сайта
			$row['acl']['categories'] = $this->getRoleCategoriesAcl($row['roleid']);
			
			// Даты и дни рождения
			if(!empty($row['birthday'])) $row['birthday'] = date('d.m.Y',strtotime($row['birthday']));
			$row['signed'] = date('d.m.Y H:i',$row['signed']);
			$row['lastvisit'] = date('d.m.Y H:i',$row['lastvisit']);
				
			return $row;
			
		}
			
		return false;
			
	}
	
	
	/**
	 * Возвращает ACL-таблицу доступа к MVC-ресурсам роли
	 * 
	 * @param int $roleid Id роли
	 *
	 * @return array
	 */
		
	public function getRoleMvcAcl($roleid) {
		
		$out = array();
		
		$select = $this->_db->select()->distinct()
				-> from(array("AA"=>"pw_mvc_acl"),array())
				-> joinLeft(array("BB"=>"pw_mvc_resources"),"AA.resourceid=BB.resourceid",
							array("module","controller","action"))
				-> where("roleid=".(int)$roleid);
	
		foreach ($this->_db->fetchAll($select) as $v) {
			$out[$v["module"]][$v["controller"]][$v["action"]] = true;
		}
		
		return $out;
		
	}
	
	
	/**
	 * Возвращает ACL-таблицу доступа к разделам сайта роли
	 * 
	 * @param int $roleid Id роли
	 *
	 * @return array
	 */
		
	public function getRoleCategoriesAcl($roleid) {
		
		$out = array();
		
		$select = $this->_db->select()
				-> from("pw_categories_acl",array("categoryid","accesslevel"))
				-> where("roleid=".(int)$roleid);
		
		return $this->_db->fetchPairs($select);
		
	}
	
	
	/**
	 * Проверяет доступ по IP-адресам для пользователя
	 * 
	 * @param array $UserInfo
	 * 
	 * @return boolean
	 */
		
	public function isAllowedHost($UserInfo) {
			
		// Получаем список разрешенных IP для роли пользователя
		$select = $this->_db->select()
				-> from("pw_roles",new Zend_Db_Expr("GROUP_CONCAT(REPLACE(allowedhosts,' ','') SEPARATOR ',')"))
				-> where("roleid=?",$UserInfo["roleid"]);
			
		// И объединяем его со списком IP пользователя
		$ListIPs = $UserInfo["allowedhosts"].",".$this->_db->fetchOne($select);
						
		// Обрабатываем список полученных IP

		if($Alloweds = explode(",",$ListIPs)) {
					
			$UserIP = explode(".",$this->_request->getClientIp());
					
			foreach ($Alloweds as $record) {
						
				if(strlen(trim($record))<7) continue;
						
				$Arr = explode(".",trim($record));
						
				foreach ($Arr as $key => $value) {
					
					if($value=="*") {
						
						$Arr[$key] = "y";
						
					} elseif (preg_match("#^([0-9]+)-([0-9]+)$#",$value,$nums)) {
						
						$Arr[$key] = (int)$nums[1]<=(int)$UserIP[$key] && (int)$nums[2]>=(int)$UserIP[$key] ? "y" : "n";
						
					} else {
						
						$Arr[$key] = $value==$UserIP[$key] ? "y" : "n";
						
					}
					
				}
						
				if(implode("",$Arr)!="yyyy") return false;
			}

		}
			
		return true;
			
	}
	
	
	/**
	 * Форма регистрации пользователей
	 * 
	 * @return Zend_Form
	 */
		
	public function getSignupForm() {
			
		// Имя пользователя
			
		$username = new Zend_Form_Element_Text("username");
			
		$username -> setRequired(true)
			   -> addValidator("NotEmpty",true,array("messages" => array("isEmpty" => $this->_translate->_("Не заполнено имя пользователя"))))
			   -> addValidator("Callback",true,array("callback" => array($this,"validateUsernameExist"), "messages" => array("callbackValue" => $this->_translate->_("Пользователь с таким именем уже зарегистрирован"))))
			   -> setLabel($this->_translate->_("Выберите имя пользователя"));
				
		// Пароль  
			
		$passw = new Zend_Form_Element_Password("passw");
			
		$passw -> setRequired(true)
			   -> addValidator("NotEmpty",true,array("messages" => array("isEmpty" => $this->_translate->_("Не выбран пароль"))))
			   -> setLabel($this->_translate->_("Выберите пароль"));
			   
		// Подтверждение пароля  
			
		$repassw = new Zend_Form_Element_Password("repassw");
			
		$repassw -> setRequired(true)
			   -> addValidator("NotEmpty",true,array("messages" => array("isEmpty" => $this->_translate->_("Не подтвержден пароль"))))
			   -> addValidator("Callback",true,array("callback" => array($this,"validatePasswConfirm"), "messages" => array("callbackValue" => $this->_translate->_("Неверно подтвержден пароль"))))
			   -> setLabel($this->_translate->_("Подтвердите пароль"));
				   
		// Email
			
		$email = new Zend_Form_Element_Text("email");
			
		$email -> setRequired(true)
			   -> addValidator("NotEmpty",true,array("messages" => array("isEmpty" => $this->_translate->_("Не указан адрес электронной почты"))))
			   -> addValidator("EmailAddress",true,array("messages" => 
				   											array("emailAddressInvalid" => $this->_translate->_("Неверно указан адрес электронной почты"),
				   												  "emailAddressInvalidFormat" => $this->_translate->_("Адрес электронной почты имеет неверный формат"))))
			   -> addValidator("Callback",true,array("callback" => array($this,"validateEmailExist"), "messages" => array("callbackValue" => $this->_translate->_("Пользователь с таким email уже зарегистрирован"))))
			   -> setLabel($this->_translate->_("Адрес электронной почты"));
			   
		// Подтверждение email
		
		$reemail = new Zend_Form_Element_Text("reemail");
			
		$reemail -> setRequired(true)
			   -> addValidator("NotEmpty",true,array("messages" => array("isEmpty" => $this->_translate->_("Не подтвержден адрес электронной почты"))))
			   -> addValidator("Callback",true,array("callback" => array($this,"validateEmailConfirm"), "messages" => array("callbackValue" => $this->_translate->_("Неверно подтвержден адрес электронной почты"))))
			   -> setLabel($this->_translate->_("Подтвердите адрес электронной почты"));
				     
		// CAPTCHA  
				    
		$captcha = Phorm_Captcha_Image::getCaptcha();
			
		$captcha -> setOptions(array("messages" => array("badCaptcha" => $this->_translate->_("Неверно указан код подтверждения"),
														 "missingID" => $this->_translate->_("Неверный идентификатор Captcha"))));
				    
		$captcha = new Zend_Form_Element_Captcha("captcha",array("captcha" => $captcha));
		$captcha -> setLabel($this->_translate->_("Код подтверждения"))
				 -> setDescription($this->_translate->_("Введите данные с изображения (цифры и латинские буквы)"));
			
		// Создаем объект формы и добавляем в нее элементы 
			  
		$form = Phorm_Form::makeForm();
			
		$form -> addElement($username)
			  -> addElement($passw)
			  -> addElement($repassw)
			  -> addElement($email)
			  -> addElement($reemail)
			  -> addElement($captcha);
			  
		$form -> addElement(new Zend_Form_Element_Submit("Send",$this->_translate->_("Зарегистрировать")));
			
		return $form;
			
	}
	
	
	/**
	 * Сохранение данных о пользователе
	 * 
	 * @param array $data Массив данных для сохранения
	 * 
	 * @return array Массив данных о пользователе
	 */
		
	public function saveUserInfo($data) {
		
		/**
		 * Выделяем из массива данных поля для вставки в основную таблицу pw_users
		 * Фильтруем необходимые поля
		 */
		
		$maindata = $this->getFilteredValuesByTableCols('pw_users',$data);
		
		foreach ($maindata as $fieldname=>$value) {
			if($fieldname=='passw') {
				if(!empty($value)) $maindata[$fieldname] = md5($value); else unset($maindata[$fieldname]);
			} elseif($fieldname=='birthday') {
				if(!empty($value)) $maindata[$fieldname] = date('Y-m-d',strtotime($value)); else $maindata[$fieldname] = null;
			}
		}
		
		/**
		 * Сохранение данных
		 */
		
		$this->_db->beginTransaction();

		try {
			
			if(isset($data['userid'])) {
				
				// Обновление существующей записи
				$this->_db->update('pw_users',$maindata,'userid='.(int)$data['userid']);
				$action = 'update';
				
			} else {
				
				// Создание новой записи
				$maindata['signed'] = time();
				$this->_db->insert('pw_users',$maindata);
				$data['userid'] = $this->_db->lastInsertId();
				$action = 'insert';
				
			}
			
			/**
			 * Логирование
			 */
			$this->setLog($action,
				array('url' => '/control/users/edit/userid/' . (int)$data['userid'] . '/',
					  'name' => $data['username'],
					  'tablename' => 'pw_users',
					  'identity' => (int)$data['userid'],
					  'fieldname' => 'userid'));
			
			$this->_db->commit();
			
			return $data;
			
		} catch (Exception $e) {
			
			$this->_db->rollBack();
			$this->_errors[] = $e->getMessage();
			
		}
		
		return false;
			
	}
	
	
	/**
	 * Отправка письма пользователю о регистрации с кодом активации
	 * 
	 * @param array $data Массив данных о пользователе
	 * 
	 * @return null
	 */
		
	function sendSignupEmail($data) {
				
		$search  = array('%sitename%','%username%','%passw%','%activeurl%','%siteurl%','%userid%','%token%');
		$replace = array($this->_cnf["site"]["name"],
						 $data["username"],
						 $data["passw"],
						 'http://'.$_SERVER['HTTP_HOST'].$this->_cnf["site"]["url"].'/users/activate/userid/'.$data["userid"].'/token/'.$data["tokenvisible"],
						 'http://'.$_SERVER['HTTP_HOST'].$this->_cnf["site"]["url"],
						 $data["userid"],
						 $data["tokenvisible"]);
								 
		$subject = str_replace($search,$replace,$this->_translate->_('Тема сообщения: Ваша регистрация на сайте'));
		$body    = str_replace($search,$replace,$this->_translate->_('Текст сообщения: Ваша регистрация на сайте'));
				
		if(isset($this->_cnf["resources"]["mail"]["charset"]) && $this->_cnf["resources"]["mail"]["charset"]!="UTF-8") {
			$subject = mb_convert_encoding($subject,$this->_cnf["resources"]["mail"]["charset"],"UTF-8");
			$body = mb_convert_encoding($body,$this->_cnf["resources"]["mail"]["charset"],"UTF-8");
			$charset = $this->_cnf["resources"]["mail"]["charset"];
		} else {
			$charset = 'UTF-8';
		}
					
		$mail = new Zend_Mail($charset);
		$mail->setBodyText($body);
		$mail->addTo($data["email"]);
		$mail->setSubject($subject);
		$mail->send();
			
	}
	
	
	/**
	 * Форма изменения профиля
	 * 
	 * @return Zend_Form
	 */
		
	function getProfileForm() {
		
		$form = new Phorm_Form('forms/default/users.xml','profile');
		
		/**
		 * Добавляем справочники на элементы
		 */
		
		if(isset($form->timezone)) {
			$form->timezone->addMultioptions(array(null=>$this->_translate->_('---Выберите---')) + $this->getTimezonesList());
		}
		
		return $form;
			
	}
	
	
	/**
	 * Возвращает список часовых поясов
	 * 
	 * @return array
	 * @todo мб дергать из Zend_locale (хотя я вроде это уже пробовал, там не везде полные данные)
	 */
		
	function getTimezonesList() {
		
		$select = $this->_db->select()
				-> order('offset')
				-> order('title');
				
		if(Zend_Registry::isRegistered('Zend_Locale') && Zend_Registry::get('Zend_Locale')->getLanguage() != 'ru') {
			$select -> from('pw_timezones',
					new Zend_Db_Expr("CONCAT(continent,'/',region), CONCAT('[',offset_text,'] ',continent,'/',region)"));
		} else {
			$select -> from('pw_timezones',
					new Zend_Db_Expr("CONCAT(continent,'/',region), CONCAT('[',offset_text,'] ',title)"));
		}
				
		return $this->_db->fetchPairs($select);
			
	}
	
	
	/**
	 * Возвращает список существующих в системе ролей
	 *
	 * @return array
	 */
	
	public function getRolesList() {
		
		$select = $this->_db->select()
				-> from(array('AA'=>'pw_roles'),new Zend_Db_Expr("AA.*,(SELECT COUNT(*) FROM pw_users WHERE roleid=AA.roleid AND isexist='yes') users, 1 actions"))
				-> where('isexist=?','yes')
				-> order('rolename');
				
		return $this->_db->fetchAll($select);
		
	}
	
	
	/**
	 * Возвращает список существующих в системе ролей в виде пар id_роли=>название
	 *
	 * @return array
	 */
	
	public function getRolesListAsPairs() {
		
		$select = $this->_db->select()
				-> from('pw_roles',array('roleid','rolename'))
				-> where('isexist=?','yes')
				-> order('rolename');
				
		return $this->_db->fetchPairs($select);
		
	}
	
	
	/**
	 * Возвращает информацию о роли
	 *
	 * @param int $roleid
	 * @return array
	 */
	
	public function getRoleInfo($roleid) {
		
		$select = $this->_db->select()
				-> from('pw_roles')
				-> where('isexist=?','yes')
				-> where('roleid=?',$roleid);
				
		return $this->_db->fetchRow($select);
		
	}
	
	
	/**
	 * Сохранение данных о группе
	 * 
	 * @param array $data Массив данных для сохранения
	 * 
	 * @return array Массив данных о группе
	 */
		
	public function saveRoleInfo($data) {
		
		/**
		 * Выделяем из массива данных поля для вставки в основную таблицу pw_roles
		 */
		
		$maindata = $this->getFilteredValuesByTableCols('pw_roles',$data);
		
		/**
		 * Сохранение данных
		 */
		
		$this->_db->beginTransaction();

		try {
			
			/**
			 * Гостевая, административная группа и группа по умолчанию должны быть в единственном кол-ве
			 */
			
			foreach (array('isdefault','isguest','isadmin') as $role) {
				if(isset($maindata[$role]) && $maindata[$role]=='yes') $this->_db->update('pw_roles',array($role=>'no'));
			}
			
			/*if(isset($maindata['isdefault']) && $maindata['isdefault']=='yes') {
				$this->_db->update('pw_roles',array('isdefault'=>'no'));
			}
			
			if(isset($maindata['isguest']) && $maindata['isguest']=='yes') {
				$this->_db->update('pw_roles',array('isguest'=>'no'));
			}
			
			if(isset($maindata['isadmin']) && $maindata['isadmin']=='yes') {
				$this->_db->update('pw_roles',array('isadmin'=>'no'));
			}*/
			
			/**
			 * Сохранение данных
			 */
			
			if(isset($data['roleid'])) {
				
				// Обновление существующей записи
				$this->_db->update('pw_roles',$maindata,'roleid='.(int)$data['roleid']);
				$action = 'update';
				
			} else {
				
				// Создание новой записи
				$this->_db->insert('pw_roles',$maindata);
				$data['roleid'] = $this->_db->lastInsertId();
				$action = 'insert';
				
			}
			
			/**
			 * Права доступа к разделам
			 */
			
			if(isset($data['acl_categories']) && is_int($data['acl_categories'])) {
				
				$this->_db->delete('pw_categories_acl','roleid='.(int)$data['roleid']);
			
				$Categories = new Phorm_Categories();
				
				foreach ($Categories->getFullList() as $category) {
					$this->_db->insert('pw_categories_acl',array(
										'categoryid' => $category['categoryid'],
										'roleid' => $data['roleid'],
										'accesslevel' => $data['acl_categories']));
				}
			
			}
			
			/**
			 * Права доступа к виджетам
			 * @todo Сделать в виджетах методы типа pack и unpack
			 */
			
			if(isset($data['acl_widgets']) && in_array($data['acl_widgets'],array('yes','no'))) {
				
				$Widget = new Phorm_Widget();
				
				foreach ($Widget->getWidgetsSetsFullList() as $set) {
					
					if(!empty($set['widgets']) && $widgets = Zend_Json::decode($set['widgets'])) {
					
						foreach ($widgets as $layoutname=>$layout) {
							
							foreach ($layout as $i=>$widget) {
								
								$widget = Zend_Json::decode($widget);
								
								$aclid = array_search($data['roleid'],$widget['acl']);
								if($aclid !== false) unset($widget['acl'][$aclid]);
								
								if($data['acl_widgets']=='yes') {
									$widget['acl'][] = $data['roleid'];
								}
								
								$widget['acl'] = array_values($widget['acl']);
								
								$widgets[$layoutname][$i] = Zend_Json::encode($widget);
								
							}
						
						}
						
						$this->_db->update('pw_widgets_sets',array('widgets'=>Zend_Json::encode($widgets)),'setid='.(int)$set['setid']);
					
					}	
					
				}
				
			}
			
			/**
			 * Логирование
			 */
			$this->setLog($action,
				array('url' => '/control/users/roleedit/roleid/' . (int)$data['roleid'] . '/',
					  'name' => $data['rolename'],
					  'tablename' => 'pw_roles',
					  'identity' => (int)$data['roleid'],
					  'fieldname' => 'roleid'));
			
			$this->_db->commit();
			
			return $data;
			
		} catch (Exception $e) {
			
			$this->_db->rollBack();
			$this->_errors[] = $e->getMessage();
			
		}
		
		return false;
			
	}
	
	
	/**
	 * Удаляет пользователя + заносит запись в лог
	 * 
	 * @param array $RoleInfo Массив данных о роли
	 * 
	 * @return boolean
	 * @todo Сделать логирование операций над пользователями и ролями
	 */
		
	function deleteRole($RoleInfo) {
			
		$this->_db->beginTransaction();
		
		try {
			
			$this->_db->update('pw_roles',array('isexist'=>'no'),'roleid='.(int)$RoleInfo['roleid']);
			
			/**
			 * Логирование
			 */
			$this->setLog('delete',
				array('url' => '/control/users/roleedit/roleid/' . (int)$RoleInfo['roleid'] . '/',
					  'name' => $RoleInfo['rolename'],
					  'tablename' => 'pw_roles',
					  'identity' => (int)$RoleInfo['roleid'],
					  'fieldname' => 'roleid'));
			
			$this->_db->commit();
			
			return true;
			
		} catch (Exception $e) {
			
			$this->_db->rollBack();	
			$this->_errors[] = $e->getMessage();
			
		}
			
		return false;
			
	}
	
	
	/**
	 * Возвращает список существующих в системе уровней прав доступа 
	 *
	 * @return array
	 */
	
	public function getAccessLevels() {
		
		$select = $this->_db->select()
				-> from('pw_access_levels')
				-> order('accesslevel');
				
		return $this->_db->fetchAll($select);
		
	}
	
	
	/**
	 * Возвращает список существующих в системе уровней прав доступа в виде пар уровень=>имя
	 *
	 * @return array
	 */
	
	public function getAccessLevelsAsPairs() {
		
		$select = $this->_db->select()
				-> from('pw_access_levels',array('accesslevel','accessname'))
				-> order('accesslevel');
				
		return $this->_db->fetchPairs($select);
		
	}
	
	
	/**
	 * Возвращает форму добавления/редактирования пользователей и ролей
	 *
	 * @param string $section
	 * @return Zend_Form
	 */
	
	public function getUserForm($section = 'add') {
		
		$form = new Phorm_Form('forms/control/users.xml',$section);
		
		/**
		 * Добавляем справочники на элементы
		 */
		
		if(isset($form->timezone)) {
			$form->timezone->addMultioptions(array(null=>$this->_translate->_('---Выберите---')) + $this->getTimezonesList());
			$form -> setDefault('timezone',date_default_timezone_get());
		}
		
		if(isset($form->roleid)) {
			$form->roleid->addMultioptions(array(null=>$this->_translate->_('---Выберите---')) + $this->getRolesListAsPairs());
		}
		
		if(isset($form->acl_categories)) {
			$form->acl_categories->addMultioptions(array(null=>$this->_translate->_('Без изменений')) + $this->getAccessLevelsAsPairs());
		}
		
		return $form;
		
	}
	
	
	/**
	 * Удаляет пользователя + заносит запись в лог
	 * 
	 * @param array $UserInfo Массив данных пользователя
	 * 
	 * @return boolean
	 * @todo Сделать логирование операций над пользователями
	 */
		
	function deleteUser($UserInfo) {
			
		$this->_db->beginTransaction();
		
		try {
			
			$this->_db->update('pw_users',array('isexist'=>'no'),'userid='.(int)$UserInfo['userid']);
			
			/**
			 * Логирование
			 */
			$this->setLog('delete',
				array('url' => '/control/users/edit/userid/' . (int)$UserInfo['userid'] . '/',
					  'name' => $UserInfo['username'],
					  'tablename' => 'pw_users',
					  'identity' => (int)$UserInfo['userid'],
					  'fieldname' => 'userid'));
			
			$this->_db->commit();
			
			return true;
			
		} catch (Exception $e) {
			
			$this->_db->rollBack();	
			$this->_errors[] = $e->getMessage();
			
		}
			
		return false;
			
	}
	
}